﻿using FileZillaConfig.ConfigClasses;
using FileZillaConfig.Types;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Xml.XPath;

namespace FileZillaConfig
{
    public class FileZillaXML
    {
        protected ServerSettings _settings;
        protected List<Group> _groups;
        protected List<User> _users;

        private XDocument xdoc;
        private string currentXPath;

        /// <summary>
        /// Generate XML document with current settings
        /// </summary>
        protected XDocument _getXML()
        {
            XDocument _configXML = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));

            XElement FileZillaServer = new XElement("FileZillaServer");
            XElement Settings = this._settings.getXML();
            XElement Users = new XElement("Users");
            foreach (User user in this._users){ Users.Add(user.getXML()); }
            XElement Groups = new XElement("Groups");
            foreach (Group gr in this._groups){ Groups.Add(gr.getXML()); }

            FileZillaServer.Add(Users);
            FileZillaServer.Add(Groups);
            FileZillaServer.Add(Settings);

            _configXML.Add(FileZillaServer);

            return _configXML;
        }

        protected void _setConfigFromXML(XDocument xmldoc)
        {
            xdoc = xmldoc;

            #region SETTINGS
            currentXPath = "FileZillaServer/Settings/Item";

            //GENERAL
            _settings.ServerPorts.portString = xmlTo<string>("Serverports");
            _settings.NumberOfThreads = xmlTo<int>("Number of Threads");
            _settings.MaxUsers = xmlTo<int>("Maximum user count");
            _settings.ConnectionTimeout = xmlTo<int>("Timeout");
            _settings.NoTransferTimeout = xmlTo<int>("No Transfer Timeout");
            _settings.WelcomeMessage = xmlTo<string>("Initial Welcome Message");
            _settings.HideWelcomeMessage = xmlTo<bool>("Hide Welcome Message");
            _settings.LoginTimeout = xmlTo<int>("Login Timeout");
            _settings.IPBindings = xmlTo<string>("IP Bindings");
            _settings.IPFilter.Allowed = xmlTo<string>("IP Filter Allowed");
            _settings.IPFilter.Disallowed = xmlTo<string>("IP Filter Disallowed");

            //SECURITY
            _settings.Security.AllowIncoming = xmlTo<bool>("Allow Incoming FXP");
            _settings.Security.AllowOutgoing = xmlTo<bool>("Allow outgoing FXP");
            _settings.Security.StrictIPFilterIncoming = xmlTo<bool>("No Strict In FXP");
            _settings.Security.StrictIPFilterOutgoing = xmlTo<bool>("No Strict Out FXP");

            //MISCELLANEOUS
            _settings.Miscellaneous.ShowPassInLog = xmlTo<bool>("Show Pass in Log");
            _settings.Miscellaneous.AllowSharedWrite = xmlTo<bool>("Allow shared write");
            _settings.Miscellaneous.InternalBufferSize = xmlTo<int>("Buffer Size");
            _settings.Miscellaneous.SocketBufferSize = xmlTo<int>("Network Buffer Size");
            _settings.Miscellaneous.IgnoreUnroutableIP = xmlTo<bool>("Active ignore local");

            //PASSIVE
            _settings.Passive.CustomIPMode = xmlTo<IPMode>("Custom PASV IP type");
            _settings.Passive.CustomIP = xmlTo<string>("Custom PASV IP");
            int pMin = xmlTo<int>("Custom PASV min port");
            int pMax = xmlTo<int>("Custom PASV max port");
            if (pMin > 0 && pMax > 0)
            {
                _settings.Passive.setPortRange(pMin, pMax);
            }
            _settings.Passive.ExternalIPServer = xmlTo<string>("Custom PASV IP server");
            _settings.Passive.UseCustomPortRange = xmlTo<bool>("Use custom PASV ports");
            _settings.Passive.NoExternalIPOnLocal = xmlTo<bool>("No External IP On Local");

            //ADMIN
            _settings.Admin.Port = xmlTo<int>("Admin port");
            _settings.Admin.Password.storePassword(xmlTo<string>("Admin Password"));
            _settings.Admin.IPBindings = xmlTo<string>("Admin IP Bindings");
            _settings.Admin.IPAdresses = xmlTo<string>("Admin IP Addresses");

            //LOGGING
            _settings.Logging.Enabled = xmlTo<bool>("Enable logging");
            _settings.Logging.SizeLimit = xmlTo<int>("Logsize limit");
            _settings.Logging.FileType = xmlTo<LoggingType>("Logfile type");
            _settings.Logging.DeleteOldFilesAfter = xmlTo<int>("Logfile delete time");

            //GSS
            _settings.GSSKerberosEnable = xmlTo<bool>("Use GSS Support");
            _settings.GSSPromptPwd = xmlTo<bool>("GSS Prompt for Password");

            //MODE Z
            _settings.ModeZCompression.Enabled = xmlTo<bool>("Mode Z Use");
            _settings.ModeZCompression.MinimumCompression = xmlTo<int>("Mode Z min level");
            _settings.ModeZCompression.MaximumCompression = xmlTo<int>("Mode Z max level");
            _settings.ModeZCompression.AllowLocal = xmlTo<bool>("Mode Z allow local");
            _settings.ModeZCompression.DisallowedIPs = xmlTo<string>("Mode Z disallowed IPs");

            //SSL
            _settings.SSL.Enabled = xmlTo<bool>("Enable SSL");
            _settings.SSL.AllowExplicitFTP = xmlTo<bool>("Allow explicit SSL");
            _settings.SSL.SSLKeyFile = xmlTo<string>("SSL Key file");
            _settings.SSL.SSLCertFile = xmlTo<string>("SSL Certificate file");
            _settings.SSL.ImplicitSSLPorts.portString = xmlTo<string>("Implicit SSL ports");
            _settings.SSL.DisallowUnencrypted = xmlTo<bool>("Force explicit SSL");
            _settings.SSL.ForcePROTPEncryption = xmlTo<bool>("Force PROT P");
            _settings.SSL.KeyPassword = xmlTo<string>("SSL Key Password");

            //AUTOBAN
            _settings.Autoban.Enabled = xmlTo<bool>("Autoban enable");
            _settings.Autoban.Attempts = xmlTo<int>("Autoban attempts");
            _settings.Autoban.type = xmlTo<int>("Autoban type");
            _settings.Autoban.Time = xmlTo<int>("Autoban time");

            //SPEED LIMITS
            _settings.SpeedLimits.DownloadLimit = xmlTo<int>("Download Speedlimit");
            _settings.SpeedLimits.DownloadType = xmlTo<SpeedLimitType>("Download Speedlimit Type");
            _settings.SpeedLimits.UploadLimit = xmlTo<int>("Upload Speedlimit");
            _settings.SpeedLimits.UploadType = xmlTo<SpeedLimitType>("Upload Speedlimit Type");
            _settings.SpeedLimits.UploadLimitRules = getXMLRule("FileZillaServer/Settings/SpeedLimits/Upload/Rule");
            _settings.SpeedLimits.DownloadLimitRules = getXMLRule("FileZillaServer/Settings/SpeedLimits/Download/Rule"); 
            #endregion

            #region USERS AND GROUPS

            _set(_users, "User");
            _set(_groups, "Group");

            #endregion

            xdoc = null;

        }

        private void _set(dynamic prop, string itemName)
        {
            string Xpath = "FileZillaServer/" + itemName + "s/" + itemName;
            IEnumerable<XElement> elem = xdoc.XPathSelectElements(Xpath);

            foreach (XElement xUser in elem)
            {
                object[] args = new object[1];
                args[0] = xUser.Attribute("Name").Value;


                dynamic _item = System.Reflection.Assembly.GetCallingAssembly().
                                CreateInstance("FileZillaConfig.ConfigClasses." + itemName, 
                                false, System.Reflection.BindingFlags.CreateInstance, null, args, null, null);

                //dynamic _item = new User(xUser.Attribute("Name").Value);
                currentXPath = Xpath+"[@Name='" + _item.Name + "']/Option";
                string _tag = "Name";

                //Users only params
                try
                {
                    _item.Password.storePassword(xmlTo<string>("Pass", _tag));
                    _item.Group = xmlTo<string>("Group", _tag);
                }
                catch (Exception) { }
                
                //Check if bool types are Boolean or CheckType
                if (_item.Enabled is Boolean)
                {
                    _item.Enabled = xmlTo<bool>("Enabled", _tag);
                    _item.BypassUserLimit = xmlTo<bool>("Bypass server userlimit", _tag);
                    _item.ForceSSL = xmlTo<bool>("ForceSsl", _tag);
                    _item.BypassServerDownloadLimit = Convert.ToBoolean(Convert.
                                                    ToInt16(xdoc.XPathSelectElement(currentXPath + "/../SpeedLimits").
                                                    Attribute("ServerDlLimitBypass").Value));
                    _item.BypassServerUploadLimit = Convert.ToBoolean(Convert.
                                                        ToInt16(xdoc.XPathSelectElement(currentXPath + "/../SpeedLimits").
                                                        Attribute("ServerUlLimitBypass").Value));
                }
                else
                {
                    _item.Enabled = xmlTo<CheckType>("Enabled", _tag);
                    _item.BypassUserLimit = xmlTo<CheckType>("Bypass server userlimit", _tag);
                    _item.ForceSSL = xmlTo<CheckType>("ForceSsl", _tag);
                    _item.BypassServerDownloadLimit = (CheckType)Convert.
                                                    ToInt16(xdoc.XPathSelectElement(currentXPath + "/../SpeedLimits").
                                                    Attribute("ServerDlLimitBypass").Value);
                    _item.BypassServerUploadLimit = (CheckType)Convert.
                                                        ToInt16(xdoc.XPathSelectElement(currentXPath + "/../SpeedLimits").
                                                        Attribute("ServerUlLimitBypass").Value);
                }
                
                _item.MaximumConnection = xmlTo<int>("User Limit", _tag);
                _item.ConnectionLimitPerIP = xmlTo<int>("IP Limit", _tag);
                _item.Comments = xmlTo<string>("Comments", _tag);

                //SPEED LIMITS
                _item.SpeedLimits.DownloadLimit = Convert.ToInt32(xdoc.XPathSelectElement(currentXPath + "/../SpeedLimits").
                                                    Attribute("DlLimit").Value);
                _item.SpeedLimits.UploadLimit = Convert.ToInt32(xdoc.XPathSelectElement(currentXPath + "/../SpeedLimits").
                                                    Attribute("UlLimit").Value);
                _item.SpeedLimits.DownloadType = (SpeedLimitType)Convert.
                                                    ToInt16(xdoc.XPathSelectElement(currentXPath + "/../SpeedLimits").
                                                    Attribute("DlType").Value);
                _item.SpeedLimits.UploadType = (SpeedLimitType)Convert.
                                                    ToInt16(xdoc.XPathSelectElement(currentXPath + "/../SpeedLimits").
                                                    Attribute("UlType").Value);

                //SPEED LIMITS RULES
                _item.SpeedLimits.DownloadLimitRules = getXMLRule(currentXPath + "/../SpeedLimits/Download/Rule");
                _item.SpeedLimits.UploadLimitRules = getXMLRule(currentXPath + "/../SpeedLimits/Upload/Rule");

                //FILTERS
                _item.IPFilter.Allowed = xdoc.XPathSelectElement(currentXPath + "/../IpFilter/Allowed").Value;
                _item.IPFilter.Disallowed = xdoc.XPathSelectElement(currentXPath + "/../IpFilter/Disallowed").Value;

                //PERMISSIONS
                IEnumerable<XElement> xPerms = xdoc.XPathSelectElements(currentXPath + "/../Permissions/Permission");
                string xp = currentXPath;
                foreach (XElement xPerm in xPerms)
                {
                    Folder folder = new Folder(xPerm.Attribute("Dir").Value);
                    currentXPath = xp + "/../Permissions/Permission[@Dir='" + folder.Path + "']/Option";

                    ///PERMISSION OPTIONS
                    folder.Autocreate = xmlTo<bool>("AutoCreate", "Name");
                    folder.IsHome = xmlTo<bool>("IsHome", "Name");

                    ///SET PERMISSIONS
                    int permSum = 0;
                    var pList = Enum.GetValues(typeof(Permission));
                    foreach (Permission permItem in pList)
                    {
                        if (xmlTo<bool>(permItem.ToString(), "Name"))
                        {
                            permSum += (int)Enum.Parse(typeof(Permission), permItem.ToString());
                        }        
                    }
                    folder.Permissions = (Permission)permSum;

                    ///ALIASES
                    IEnumerable<XElement> xAliases = xdoc.XPathSelectElements(xp + "/../Permissions/Permission[@Dir='" + folder.Path + "']/Aliases/Alias");
                    foreach (XElement xAlias in xAliases)
                    {
                        folder.Aliases.Add(xAlias.Value);
                    }

                    _item.Folders.List.Add(folder);
                }

                prop.Add(_item);
            }
        }

        private List<LimitCriteria> getXMLRule(string path)
        {
            IEnumerable<XElement> list = xdoc.XPathSelectElements(path);
            List<LimitCriteria> Limits = new List<LimitCriteria>();

            foreach (XElement xRule in list)
            {
                LimitCriteria Rule = new LimitCriteria();
                Rule.Speed = Convert.ToInt32(xRule.Attribute("Speed").Value);
                Rule.WeekDays = (WeekDays)Convert.ToInt32(xRule.XPathSelectElement("./Days").Value);
                if (xRule.XPathSelectElement("./Date") != null)
                {
                    int Year = Convert.ToInt32(xRule.XPathSelectElement("./Date").Attribute("Year").Value);
                    int Month = Convert.ToInt32(xRule.XPathSelectElement("./Date").Attribute("Month").Value);
                    int Day = Convert.ToInt32(xRule.XPathSelectElement("./Date").Attribute("Day").Value);

                    Rule.Date = new DateTime(Year, Month, Day);
                }
                if (xRule.XPathSelectElement("./From") != null)
                {
                    int Hour = Convert.ToInt32(xRule.XPathSelectElement("./From").Attribute("Hour").Value);
                    int Minute = Convert.ToInt32(xRule.XPathSelectElement("./From").Attribute("Minute").Value);
                    int Second = Convert.ToInt32(xRule.XPathSelectElement("./From").Attribute("Second").Value);

                    Rule.FromTime = new TimeSpan(Hour, Minute, Second);
                }
                if (xRule.XPathSelectElement("./To") != null)
                {
                    int Hour = Convert.ToInt32(xRule.XPathSelectElement("./To").Attribute("Hour").Value);
                    int Minute = Convert.ToInt32(xRule.XPathSelectElement("./To").Attribute("Minute").Value);
                    int Second = Convert.ToInt32(xRule.XPathSelectElement("./To").Attribute("Second").Value);

                    Rule.ToTime = new TimeSpan(Hour, Minute, Second);
                }
                

                Limits.Add(Rule);
            }

            return Limits;
        }

        private T xmlTo<T>(string name, string nameTag = "name")
        {
            string value = xdoc.XPathSelectElement(currentXPath+"[@"+nameTag+"=\"" + name + "\"]").Value;
            if (!string.IsNullOrEmpty(value)){
                if (typeof(T).IsEnum){
                    return (T)Enum.ToObject(typeof(T), Convert.ToInt32(value));    
                }
                else if (typeof(T).Name == "Boolean")
                {
                    return (T)((object)Convert.ToBoolean(Convert.ToInt16(value)));
                }
                else
                {
                    return (T)Convert.ChangeType(value, typeof(T));
                    
                }
            }
            return default(T);
        }

        

        

    }
}
